package com.zyb.beiojcodesandbox.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.dfa.FoundWord;
import cn.hutool.dfa.WordTree;
import com.zyb.beiojcodesandbox.model.ExecuteCodeRequest;
import com.zyb.beiojcodesandbox.model.ExecuteCodeResponse;
import com.zyb.beiojcodesandbox.model.ExecuteMessage;
import com.zyb.beiojcodesandbox.model.QuestionJudgeInfo;
import com.zyb.beiojcodesandbox.utils.ProcessUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


/**
 * @author 十八岁讨厌编程
 * @date 2025/2/12 11:54
 * @PROJECT_NAME beioj-code-sandbox
 * @description
 */

@Slf4j
public class JavaCodeSandboxTemplate implements CodeSandbox{

    /**
     * 存放用户代码的目录名字
     */
    private final static String USER_CODE_BOX_NAME = "usercode";
    /**
     * 用户提交代码的类名
     */
    private final static String CLASS_NAME = "Main.java";
    /**
     * 超时指标
     */
    protected static final long TIME_OUT = 5000L;

    /**
     *  Java自定义安全管理器的class文件存放处
     */
    private static final String SECURITY_MANAGER_PATH = "F:\\星球项目汇总\\OJ在线判题系统\\后端回顾2025.1\\beioj-code-sandbox\\src\\main\\resources\\security";

    /**
     * Java自定义安全管理器的类名
     */
    private static final String SECURITY_MANAGER_CLASS_NAME = "MySecurityManager";

    /**
     * 危险操作黑名单
     */
    private static final List<String> blackList = Arrays.asList("Files", "exec");

    /**
     * 初始化字典树
     */
    private static final WordTree WORD_TREE;

    static {
        // 初始化字典树
        WORD_TREE = new WordTree();
        WORD_TREE.addWords(blackList);
    }
//    测试方法
    public static void main(String[] args) {
        JavaCodeSandboxTemplate javaNativeCodeSandbox = new JavaCodeSandboxTemplate();
        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
        executeCodeRequest.setInputList(Arrays.asList("1 2", "1 12"));
        String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);

        executeCodeRequest.setCode(code);
        executeCodeRequest.setLanguage("java");
        ExecuteCodeResponse executeCodeResponse = javaNativeCodeSandbox.executeCode(executeCodeRequest);
    }




    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
        log.info("沙箱收到：" + executeCodeRequest.toString());

        //1.保存用户文件
        File userFile = saveCodeToFile(executeCodeRequest);

        //2.编译代码，得到class文件
        ExecuteCodeResponse e = compileFile(userFile);
        if (e != null) return e;

        //3.执行代码得到输出结果
        List<String> inputList = executeCodeRequest.getInputList();
        List<ExecuteMessage> outputMessages = null;
        try {
            outputMessages = runFile(inputList,userFile);
        } catch (Exception ex) {
            log.info("代码执行出错：" + getErrorResponse(ex));
            return getErrorResponse(ex);
        }

        //4.整理获取输出结果
        ExecuteCodeResponse response = getOutputResponse(outputMessages, inputList);


        //5.文件清理，释放空间
        boolean del = FileUtil.del(userFile.getParentFile());
        if (!del) {
            log.info("文件清理失败");
        } else {
            log.info("文件清理成功");
        }


        return response;
    }

    protected ExecuteCodeResponse getOutputResponse(List<ExecuteMessage> outputMessages, List<String> inputList) {
        //构建返回体
        ExecuteCodeResponse response = new ExecuteCodeResponse();
        List<String> outputList = new ArrayList<>();
        //先设为正常值，后面检测不正常设为3
        response.setStatus("1");
        //● 收集整理输出结果
        //有一个输出码不为0，则代表代码出错,顺便统计最大时间
        long maxTime = 0;
        long maxMemory = 0;
        for (ExecuteMessage outputMessage : outputMessages) {
            maxTime = Math.max(maxTime, outputMessage.getTime());
            Long memory = outputMessage.getMemory();
            if ( memory != null) {
                maxMemory = Math.max(maxMemory,memory);
            }
            Integer exitValue = outputMessage.getExitValue();
            if (exitValue != null && !exitValue.equals(0)) {
                //表示执行中存在错误i
                response.setStatus("3");
            }
        }

        if (outputMessages.size() != inputList.size()) {
            //表示执行中存在错误i
            response.setStatus("3");
        }


        //说明代码没有问题
        for (ExecuteMessage outputMessage : outputMessages) {
            if (StrUtil.isBlank(outputMessage.getMessage())) {
                response.setStatus("3");
                break;
            }
            outputList.add(outputMessage.getMessage());
        }

        response.setOutputList(outputList);
        QuestionJudgeInfo questionJudgeInfo = new QuestionJudgeInfo();
        //获取最大执行时间
        questionJudgeInfo.setTime(maxTime);
        //获取最大占用内存
        questionJudgeInfo.setMemory(maxMemory);

        response.setJudgeInfo(questionJudgeInfo);
        //● 错误处理、提高程序健壮性

        log.info("沙箱返回:" + response);
        return response;
    }

    protected ExecuteCodeResponse compileFile(File userFile) {
        String compliedCmd = String.format("javac -encoding utf-8 %s", userFile.getAbsolutePath());
        try {
            Process compileProcess = Runtime.getRuntime().exec(compliedCmd);
            ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage(compileProcess, "编译");
            log.info(executeMessage.toString());
        } catch (Exception e) {
            return getErrorResponse(e);
        }
        return null;
    }

    protected File saveCodeToFile(ExecuteCodeRequest executeCodeRequest) {
        //● 把用户的代码保存为文件
        String code = executeCodeRequest.getCode();
        //  校验代码中是否包含黑名单中的命令
        FoundWord foundWord = WORD_TREE.matchWord(code);
        if (foundWord != null) {
            System.out.println("包含禁止词：" + foundWord.getFoundWord());
            return null;
        }

        String workSpace = System.getProperty("user.dir");
        String USERS_CODE_BOX = workSpace + File.separator + USER_CODE_BOX_NAME;
        if (!FileUtil.exist(USERS_CODE_BOX)) {
            FileUtil.mkdir(USERS_CODE_BOX);
        }
        String USER_CODE_BOX = USERS_CODE_BOX + File.separator + UUID.randomUUID();
        File userFile = FileUtil.touch(USER_CODE_BOX + File.separator + CLASS_NAME);
        FileUtil.writeString(code,userFile,"UTF-8");
        return userFile;
    }

    protected List<ExecuteMessage> runFile(List<String> inputList,File userFile) throws IOException {
        ArrayList<ExecuteMessage> outputMessages = new ArrayList<>();
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int i = 0; i < inputList.size(); i++) {
            String execCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s;%s -Djava.security.manager=MySecurityManager Main %s", userFile.getParentFile().getAbsolutePath(), SECURITY_MANAGER_PATH, inputList.get(i));

            Process compileProcess = Runtime.getRuntime().exec(execCmd);

            Future<?> future = executor.submit(() -> {
                try {
                    Thread.sleep(TIME_OUT);
                    log.info("超时了，中断");
                    compileProcess.destroy();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            // 超时控制  消耗资源
//                Thread timeProtect = new Thread(() -> {
//                    try {
//                        Thread.sleep(TIME_OUT);
//                        log.info("超时了，中断");
//                        compileProcess.destroy();
//                    } catch (InterruptedException e) {
//                        throw new RuntimeException(e);
//                    }
//                });
//                timeProtect.start();
            //该方法中已经进行了计时，并且直接封装了返回结果
            ExecuteMessage executeMessage = ProcessUtils.runProcessAndGetMessage(compileProcess, "执行");
            //把每次的输出给收集起来
            log.info(executeMessage.toString());
//                timeProtect.stop();
            future.cancel(true);
            outputMessages.add(executeMessage);
        }
        executor.shutdown(); // 关闭线程池

        return outputMessages;
    }


    /**
     * 获取错误响应
     *
     * @param e
     * @return
     */
    private ExecuteCodeResponse getErrorResponse(Throwable e) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setOutputList(new ArrayList<>());
        executeCodeResponse.setMessage(e.getMessage());
        // 表示代码沙箱错误
        executeCodeResponse.setStatus("2");
        executeCodeResponse.setJudgeInfo(new QuestionJudgeInfo());
        return executeCodeResponse;
    }
}


